import os
import time
import subprocess

class ADBHelper:
    
    def __init__(self, device_serial=None, adb_path=None, minicap_port=1717):
        import shutil
        self.device_serial = device_serial
        self.device_ip = None
        self.is_connected = False
        self.is_root = False
        self.abi = None
        self.sdk = None
        self.dpi = None
        self.screen_resolution = None  
        self.device_rotation = None
        self.minicap_port = minicap_port
        self.base_path = '/data/local/tmp'
        self.minicap_path = f'{self.base_path}/minicap'
        self.minitouch_path = f'{self.base_path}/minitouch'

        # 确定adb路径
        if adb_path:
            self.adb_path = adb_path
        else:
            self.adb_path = shutil.which('adb')
            if not self.adb_path:
                raise Exception("未找到ADB路径，请通过adb_path参数指定或配置环境变量")

        try:
            subprocess.run([self.adb_path, 'version'], check=True, capture_output=True)
        except subprocess.CalledProcessError as e:
            raise Exception(f"ADB路径验证失败: {str(e)}")

    def init(self):
        """初始化数据"""
        if not self.check_connection(True):
            print("没有检测到可用设备，正在尝试自动连接")
            self.auto_connect()

        if self.is_connected:
            info = self.get_device_info()
            print(info)

    def scan_emulator_ports(self):
        """扫描常见模拟器端口"""
        ports = [7555, 62001, 5555, 21503]
        active_ports = []
        
        for port in ports:
            try:
                result = subprocess.run(
                    f'netstat -ano | findstr "{port}"', shell=True, capture_output=True, text=True, encoding='utf-8', errors='ignore'
                )
                if result.stdout and str(port) in result.stdout:
                    active_ports.append(port)
                    print(f'发现开放端口：{port}')
            except Exception as e:
                print(f'端口扫描异常({port}): {str(e)}')
        return active_ports

    def get_devices(self):
        """获取所有物理设备列表"""
        result = subprocess.run([self.adb_path, 'devices'], capture_output=True, text=True, encoding='utf-8')
        return [{'serial': parts[0], 'status': parts[1]} 
                for line in result.stdout.splitlines()[1:] 
                if len(parts := line.strip().split('\t')) == 2]

    def check_connection(self, active_check=False):
        """
        检测设备连接状态
        :param active_check: 是否执行主动验证（执行adb命令确认设备在线）
        :return: bool 连接状态
        """
        if not active_check:
            return self.is_connected

        try:
            devices = self.get_devices() or []
            connected_device = next((d for d in devices if d.get('status') == 'device'), None)
            self.device_serial = connected_device['serial'] if connected_device is not None else None
            self.is_connected = True if connected_device is not None else False
            return self.is_connected
        except Exception as e:
            print(f"连接检查失败: {str(e)}")
            self.is_connected = False
        return self.is_connected

    def auto_connect(self):
        """自动连接设备"""
        emulators = [f'127.0.0.1:{port}' for port in self.scan_emulator_ports()]
        for device in emulators:
            result = self.connect_device(device)
            if self.is_connected:
                print(f"成功连接设备: {device}")
                return True
            print(f"连接失败 {device}: {result.stdout.strip()}")
        print("所有设备连接尝试失败")
        return False

    def connect_device(self, device_serial):
        result = subprocess.run([self.adb_path, 'connect', device_serial], capture_output=True, text=True, encoding='utf-8')
        if result.stdout and 'connected' in result.stdout:
            self.is_connected = True
            self.device_serial = device_serial
        else:
            self.is_connected = False
        return result

    # =============================================================================================

    def get_device_info(self):
        """获取设备信息"""
        if not self.is_connected:
            print("设备未连接")
            return None

        self.device_ip = self.get_device_ip()
        self.abi = self.get_abi()
        self.sdk = self.get_sdk()
        self.dpi = self.get_dpi() 
        self.screen_resolution = self.get_screen_resolution() 
        self.device_rotation = self.get_device_rotation()
        return {
            'serial': self.device_serial,
            'ip': self.device_ip,
            'abi': self.abi,
            'sdk': self.sdk,
            'dpi': self.dpi,
            'screen_resolution': self.screen_resolution,
            'rotation': self.device_rotation
        }

    def get_device_ip(self):
        """通过adb shell netcfg获取真实IP"""
        output = self._execute_shell('netcfg')
        for line in output.splitlines():
            if 'wlan0' in line and 'UP' in line:
                return line.split()[2].split('/')[0]
        return None

    def get_abi(self):
        return self._execute_shell('getprop ro.product.cpu.abi')

    def get_sdk(self):
        return self._execute_shell('getprop ro.build.version.sdk')

    def get_dpi(self):
        """通过wm density获取屏幕DPI"""
        output = self._execute_shell('wm density')
        if 'Physical density:' in output:
            return output.split(':')[1].strip()
        return None

    def get_screen_resolution(self):
        """通过wm size获取屏幕分辨率"""
        output = self._execute_shell('wm size')
        if 'Physical size:' in output:
            return output.split(':')[1].strip()
        return None

    def get_device_rotation(self):
        """获取设备旋转角度"""
        output = self._execute_shell('dumpsys input | grep SurfaceOrientation')
        try:
            rotation_map = {'0':0, '1':90, '2':180, '3':270}
            raw_value = output.split(': ')[-1].strip()
            self.device_rotation = rotation_map.get(raw_value, 0)
            return self.device_rotation
        except Exception as e:
            print(f"获取旋转角度失败: {str(e)}")
            return 0

    # =============================================================================================

    def _execute_shell(self, command):
        if not os.path.exists(self.adb_path): 
            raise Exception(f'ADB路径不存在: {self.adb_path}')
        if not self.device_serial:
            raise Exception("设备未连接")
        cmd = [self.adb_path, '-s', self.device_serial, 'shell', command]
        result = subprocess.run(cmd, capture_output=True, text=True)
        return result.stdout.strip()

    def start_minicap_service(self):
        if not self.is_connected:
            raise Exception("设备未连接")

        width, height = self.screen_resolution.split('x')
        try:
            self.minicap_process = subprocess.Popen(
                [self.adb_path, '-s', self.device_serial, 'shell',
                 f'LD_LIBRARY_PATH={self.base_path} {self.minicap_path} '
                 f'-P {width}x{height}@{width}x{height}/{self.device_rotation}'],
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True
            )
            time.sleep(0.5)
            if self.minicap_process.poll() is not None:
                error = self.minicap_process.stderr.read()
                raise RuntimeError(f'minicap启动失败: {error}')
            return True
        except Exception as e:
            if self.minicap_process:
                self.minicap_process.kill()
            raise RuntimeError(f'minicap启动异常: {str(e)}')

    def list_device_files(self, directory_path):
        if not self.is_connected:
            raise Exception("设备未连接")
        
        try:
            output = self._execute_shell(f'ls -1 {directory_path}')
            if 'No such file or directory' in output:
                raise FileNotFoundError(f"目录不存在: {directory_path}")
            if 'Permission denied' in output:
                raise PermissionError(f"权限不足: {directory_path}")
            return [f for f in output.splitlines() if f]
        except subprocess.CalledProcessError as e:
            raise RuntimeError(f"执行命令失败: {str(e)}")

    def push_file(self, local_path, device_path):
        cmd = [self.adb_path, 'push', local_path, device_path]
        if self.device_serial:
            cmd = [self.adb_path, '-s', self.device_serial] + cmd[1:]
        return subprocess.run(cmd, capture_output=True, text=True)

    def pull_file(self, device_path, local_path):
        """从设备拉取文件"""
        cmd = [self.adb_path, '-s', self.device_serial, 'pull', device_path, local_path]
        result = subprocess.run(cmd, capture_output=True, text=True)
        if result.returncode != 0:
            raise RuntimeError(f'文件拉取失败: {result.stderr.strip()}')

    def push_minitouch(self):
        minitouch_path = rf'F:\workspace\MiniCapAndMiniTouch\minitouch\{self.abi}\minitouch'
        if not os.path.exists(minitouch_path):
            raise FileNotFoundError(f"minitouch文件未找到: {minitouch_path}")

        result = self.push_file(minitouch_path, self.minitouch_path)
        if result.returncode != 0:
            raise Exception(f"推送minitouch失败: {result.stderr}")

        self._execute_shell(f'chmod 777 {self.minitouch_path}')
        print("minitouch已成功推送")
        # print("权限验证结果:", self.verify_minitools().get('minitouch'))

    def push_minicap(self):
        # minicap_so_path = rf'C:\Users\123\Downloads\minicap-master\jni\minicap-shared\aosp\libs\android-{self.sdk}\{self.abi}\minicap.so'
        minicap_so_path = rf'F:\workspace\minitool\minicap\jni\minicap-shared\aosp\libs\android-{self.sdk}\{self.abi}\minicap.so'
        minicap_path = rf'F:\workspace\minitool\minicap\libs\{self.abi}\minicap'

        result = self.push_file(minicap_so_path, f'{self.base_path}/minicap.so')
        result = self.push_file(minicap_path, self.minicap_path)
        if result.returncode!= 0:
            raise Exception(f"推送minicap.so失败: {result.stderr}")
        if result.returncode!= 0:
            raise Exception(f"推送minicap失败: {result.stderr}")

        self._execute_shell(f'chmod 777 {self.minicap_path}')
        self._execute_shell('chmod 777 /data/local/tmp/minicap.so')
        print("minicap+minicap.so已成功推送")
        # print("权限验证结果:", self.verify_minitools())
    
    def verify_minitools(self, directory=None):
        directory = directory or self.base_path
        """
        验证minitouch/minicap相关文件及其权限
        返回格式: {
            'minitouch': {'exists': bool, 'permissions': str, 'executable': bool},
            'minicap': {'exists': bool, 'permissions': str, 'executable': bool},
            'minicap.so': {'exists': bool, 'permissions': str}
        }
        """
        try:
            output = self._execute_shell(f'ls -l {directory}')
            permissions = {}
            for line in output.splitlines():
                if 'minitouch' in line or 'minicap' in line:
                    parts = line.split()
                    if len(parts) >= 7:
                        filename = parts[-1]
                        perm = parts[0][1:4] + parts[0][4:7] + parts[0][7:10]
                        permissions[filename] = perm

            result = {
                'minitouch': {
                    'exists': 'minitouch' in permissions,
                    'permissions': permissions.get('minitouch', ''),
                    'executable': 'x' in permissions.get('minitouch', '')
                },
                'minicap': {
                    'exists': 'minicap' in permissions,
                    'permissions': permissions.get('minicap', ''),
                    'executable': 'x' in permissions.get('minicap', '')
                },
                'minicap.so': {
                    'exists': 'minicap.so' in permissions,
                    'permissions': permissions.get('minicap.so', '')
                }
            }
            return result
        except Exception as e:
            print(f"权限验证失败: {str(e)}")
            return {
                'minitouch': {'exists': False, 'permissions': '', 'executable': False},
                'minicap': {'exists': False, 'permissions': '', 'executable': False},
                'minicap.so': {'exists': False, 'permissions': ''}
            }

    def verify_minicap_support(self):
        if not self.is_connected:
            raise Exception("设备未连接")

        try:
            width, height = self.screen_resolution.split('x')
            command = f"LD_LIBRARY_PATH=/data/local/tmp /data/local/tmp/minicap -P {width}x{height}@{width}x{height}/{self.device_rotation} -t"
            output = self._execute_shell(command)
            return output.strip().endswith("OK")
        except subprocess.CalledProcessError as e:
            print(f"命令执行失败: {str(e)}")
            return False
        except Exception as e:
            print(f"验证异常: {str(e)}")
            return False

    def deploy_tools(self):
        """推送并部署minitouch和minicap"""
        try:
            self.push_minitouch()
            self.push_minicap()
            print("工具部署完成")
        except Exception as e:
            print(f"工具部署失败: {str(e)}")

    def setup_minicap_forward(self):
        """专用minicap端口转发"""
        if not self.is_connected:
            raise Exception("设备未连接")
        result = subprocess.run(
            [self.adb_path, '-s', self.device_serial, 'forward', 
             f'tcp:{self.minicap_port}', 'localabstract:minicap'],
            capture_output=True,
            text=True
        )
        if result.returncode != 0:
            raise Exception(f"minicap端口转发失败: {result.stderr}")
        return True

    def start_minicap_service(self):
        if not self.is_connected:
            raise Exception("设备未连接")
        
        width, height = self.screen_resolution.split('x')
        command = f'LD_LIBRARY_PATH={self.base_path} {self.minicap_path} -P {width}x{height}@{width}x{height}/{self.device_rotation}'
        
        try:
            # 使用Popen异步执行命令
            self.minicap_process = subprocess.Popen(
                [self.adb_path, '-s', self.device_serial, 'shell', command],
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE
            )
            # 非阻塞检查初始输出
            time.sleep(0.5)
            if self.minicap_process.poll() is not None:
                error_output = self.minicap_process.stderr.read().decode()
                raise RuntimeError(f'minicap启动失败: {error_output}')
            return True
        except Exception as e:
            if self.minicap_process:
                self.minicap_process.kill()
            raise RuntimeError(f'minicap启动异常: {str(e)}')

    def run_minicap(self):
        """执行完整的minicap流程"""
        try:
            # 第一步：建立端口转发
            self.setup_minicap_forward()
            print(f"成功建立minicap端口转发: {self.minicap_port}")
            
            # 第二步：启动minicap服务
            if self.start_minicap_service():
                print(f"minicap服务已启动，可通过localhost:{self.minicap_port}访问")
                return True
            return False
        except Exception as e:
            raise RuntimeError(f"minicap启动流程失败: {str(e)}")

    def capture_screenshot(self, local_path=None):
        """
        获取设备截图并保存到本地
        :param local_path: 本地保存路径，默认./images/screenshot.jpg
        """
        local_path = local_path or os.path.join(os.getcwd(), 'images', 'screenshot.jpg')
        os.makedirs(os.path.dirname(local_path), exist_ok=True)

        if not self.screen_resolution:
            self.screen_resolution = self.get_screen_resolution()
        width, height = self.screen_resolution.split('x')

        # 设备端临时文件路径
        tmp_path = f'{self.base_path}/screenshot.jpg'

        try:
            # 执行minicap命令生成设备端截图文件
            cmd = (
                f'LD_LIBRARY_PATH={self.base_path} {self.minicap_path} '
                f'-P {width}x{height}@{width}x{height}/{self.device_rotation} -s > {tmp_path}'
            )
            result = self._execute_shell(cmd)
            
            # 验证命令执行结果
            if 'minicap' in result and 'error' in result.lower():
                raise RuntimeError(f'minicap执行失败: {result}')

            # 拉取截图文件到本地
            self.pull_file(tmp_path, local_path)
            print(f'截图已保存至: {local_path}')

        except subprocess.CalledProcessError as e:
            raise RuntimeError(f'截图失败: {str(e)}')
        except FileNotFoundError as e:
            raise RuntimeError(f'截图文件未找到: {str(e)}')
        except Exception as e:
            raise RuntimeError(f'截图处理异常: {str(e)}')

# =============================================================================================

    def swipe(self, x1, y1, x2, y2, duration=500):
        """
        模拟滑动操作
        :param x1, y1: 起始坐标（支持比例值0-1或绝对像素）
        :param x2, y2: 结束坐标（支持比例值0-1或绝对像素）
        :param duration: 滑动持续时间（毫秒）
        """
        if not self.is_connected:
            raise Exception("设备未连接")

        width, height = map(int, self.screen_resolution.split('x'))
        
        # 比例坐标转换
        def convert(val, max_val):
            return int(val * max_val) if 0 < val < 1 else int(val)
        
        x1 = convert(x1, height)
        y1 = convert(y1, width)
        x2 = convert(x2, height)
        y2 = convert(y2, width)
        print(f'滑动坐标: {x1}, {y1} -> {x2}, {y2}')
        command = f'input swipe {x1} {y1} {x2} {y2} {duration}'
        self._execute_shell(command)


def main():
    adb_path = r"F:\Program Files\Netease\MuMu Player 12\shell\adb.exe"
    adb = ADBHelper(adb_path=adb_path)
 
    
    start = time.time()
    adb.init()
    # adb.push_minicap()
    # adb.verify_minicap_support()
    # print(adb.verify_minitools())
    # print(adb.start_minicap_service())
    # adb.capture_screenshot()
    adb.swipe(0.5*1280, 0.15*720, 0.5*1280, 0.85*720)
    # adb.swipe(0.5*1280, 0.85*720, 0.5*1280, 0.15*720)
    end = time.time()
    print(f"耗时: {end - start:.2f}秒")

if __name__ == "__main__":
    main()